package com.rajaev.dtu.tractor.app.scheduleTask;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import com.mongodb.client.MongoCursor;
import com.rajaev.dtu.tractor.app.service.BatteryRunInfoStaticService;
import com.rajaev.dtu.tractor.app.service.StatisticService;
import com.rajaev.dtu.tractor.domain.entity.BatteryPo;
import com.rajaev.dtu.tractor.domain.entity.BatteryRunInfoResult;
import com.rajaev.dtu.tractor.domain.repository.mysql.BatteryDao;
import com.rajaev.dtu.tractor.domain.vo.*;
import com.rajaev.dtu.tractor.infrastructure.helper.MongoDbUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;

import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.eq;
import static com.rajaev.dtu.tractor.infrastructure.base.common.Constants.dailyStaticCollectionName;
import static com.rajaev.dtu.tractor.infrastructure.base.common.Constants.hourlyStaticCollectionName;

/**
 * 定时统计车辆的运行情况，包括每日耗电电量，充电电量，开机时间等数据
 *
 * @Created with IDEA
 * @author: tianjie
 * @Date:2019/6/13
 * @Time:10:12
 */
@Component
@Slf4j(topic = "数据统计")
public class BatteryRunInfoStaticJob {

    @Autowired
    private RedisLockRegistry redisLockRegistry;

    @Autowired
    private BatteryDao batteryDao;

    @Autowired
    private BatteryRunInfoStaticService batteryRunInfoStaticService;

    @Autowired
    private StatisticService statisticService;

    @Autowired
    private MongoDbUtils mongoDbUtils;


    private String lock_key = "batteryRunInfoStaticJob:";


    private int TIME_OUT = 60;

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

    private static SimpleDateFormat sdf_hour = new SimpleDateFormat("yyyyMMddHH");

    @Value("${kg.isOpenJob}")
    private boolean isOpenJob;


    /**
     * 执行每天的统计任务
     */
    @Scheduled(fixedDelay = 1000 * 60 * 60 * 2)
    public void doDailyStatic() throws ParseException {

        if (!isOpenJob) {
            return;
        }
        //取最近7天的数据，如果有不存在的，则将不存在的日期的数据进行补上。
        List<Integer> preDates = getPreDates(5);
        for (Integer period : preDates) {
            Date date = sdf.parse(Convert.toStr(period));
            List<BatteryPo> batteryList = batteryDao.selectAllBatteryByCreateTime(date);
            List<BmsDailyStaticResult> list = getPeriodData(batteryList, period, BmsDailyStaticResult.class);
            for (BmsDailyStaticResult bmsDailyStaticResult : list) {
                saveBatteryStaticInfoInMongo(bmsDailyStaticResult);
            }
        }
    }

    /**
     * 统计当天日期的每个小时段内的充放电电量，上线时间等数据
     */
    //@Scheduled(cron = "0 0/5 * * * ?")
    public void doHourlyStatic() {/*
        if(!isOpenJob){
            return;
        }
        List<BatteryPo> list = batteryDao.selectAllBattery();
        List<Integer> preDateHours = getPreHours();
        for (Integer period : preDateHours) {
            List<BmsHourlyStaticResult> bmsDailyStaticResults = getPeriodData(list, period, BmsHourlyStaticResult.class);
            for (BmsHourlyStaticResult bmsHourlyStaticResult : bmsDailyStaticResults) {
                saveBatteryStaticInfoInMongo(bmsHourlyStaticResult);
            }
        }*/
    }

    @Scheduled(cron = "0 0/5 * * * ?")
    public void doStatisticTodayInfo() {
        if (!isOpenJob) {
            return;
        }
        List<BatteryPo> list = batteryDao.selectAllBattery();
        StatisticResultVo.TodayInfo todayInfo = getTodayInfo(list, new Date());


    }

    /**
     * 定时刷新所有的电池的运行数据，更新到mysql当中的t_battery 表当中
     */
    @Scheduled(cron = "0 0 0/1 * * *")
    public void refreshBatteryInfo() {
        if (!isOpenJob) {
            return;
        }
        List<BatteryPo> list = batteryDao.selectAllBattery();
        for (BatteryPo batteryPo : list) {
            try {
                String vin = batteryPo.getVin();
                BatteryRunInfoResult batteryRunInfoResult = statisticService.getBatteryRunInfoResultFromMongo(vin);
                batteryRunInfoResult.setBatteryId(batteryPo.getId());
                if (batteryRunInfoResult.getTotalCharge().equals(batteryPo.getTotalCharge()) &&
                        batteryRunInfoResult.getTotalDischarge().equals(batteryPo.getTotalDischarge())
                        && batteryRunInfoResult.getTotalOnlineTime().equals(batteryPo.getTotalOnlineTime())
                        && batteryRunInfoResult.getDailyAvgDischarge().equals(batteryPo.getDailyAvgDischarge())
                        && batteryRunInfoResult.getTotalDischargeTime().equals(batteryPo.getTotalDischargeTime())) {
                    continue;
                } else {
                    batteryDao.updateBatteryRunInfo(batteryRunInfoResult);
                }
            } catch (Exception e) {
                log.error("{}", e.getMessage());
            }

        }
    }


    public StatisticResultVo.TodayInfo getTodayInfo(List<BatteryPo> list, Date date) {
        AtomicReference<BigDecimal> todayDischarge = new AtomicReference<>(new BigDecimal(0));
        AtomicReference<BigDecimal> todayCharge = new AtomicReference<>(new BigDecimal(0));
        AtomicLong todayOnlineTime = new AtomicLong();
        AtomicInteger onlineNum = new AtomicInteger();
        for (BatteryPo batteryPo : list) {
            BatterySingleDeviceDailyStatistic batterySingleDeviceDailyStatistic = batteryRunInfoStaticService.staticBatterySingleDeviceDailyStatistic(batteryPo.getVin(), date);
            if (batterySingleDeviceDailyStatistic != null) {
                todayDischarge.getAndUpdate(discharge -> discharge.add(batterySingleDeviceDailyStatistic.getTodayDischarge()));
                todayCharge.getAndUpdate(charge -> charge.add(batterySingleDeviceDailyStatistic.getTotalCharge()));
                todayOnlineTime.getAndAdd(batterySingleDeviceDailyStatistic.getTodayOnlineTime());
                onlineNum.incrementAndGet();
            }
        }


        BigDecimal onlineRate = CollectionUtils.isEmpty(list) ? BigDecimal.ZERO : BigDecimal.valueOf(onlineNum.get()).divide(new BigDecimal(list.size()), 2, BigDecimal.ROUND_DOWN);
        StatisticResultVo.TodayInfo todayInfo = new StatisticResultVo.TodayInfo(todayDischarge.get(), todayCharge.get(), todayOnlineTime.get(), onlineRate.floatValue());
        return todayInfo;
    }


    /**
     * 获取时间段内的
     *
     * @param list   车辆的list
     * @param period 时间段 yyyymmdd 或者 yyyymmddhh
     * @param clazz  返回的类型
     * @param <E>
     * @return
     */
    public <E extends BmsStaticResult> List<E> getPeriodData(List<BatteryPo> list, Integer period, Class<E> clazz) {
        List<BmsStaticResult> bmsStaticResults = new ArrayList<>();
        log.debug("统计日期：{}", period);
        for (BatteryPo batteryPo : list) {

            Lock lock = redisLockRegistry.obtain(lock_key + batteryPo.getVin() + ":" + period);

            try {
                //申请分布式的锁，如果成功，则继续执行
                if (lock.tryLock(TIME_OUT, TimeUnit.SECONDS)) {
                    //
                    if (clazz.equals(BmsDailyStaticResult.class)) {
                        //查询车辆是否有当天的数据，没有则生成
                        MongoCursor<Document> mongoCursor = mongoDbUtils.getDatabase().getCollection(dailyStaticCollectionName)
                                .find(and(eq("vin", batteryPo.getVin()), eq("dataDate", period)))
                                .iterator();
                        if (mongoCursor != null && mongoCursor.hasNext()) {
                            //该小时周期内数据已经存在。
                            continue;
                        }

                        BmsDailyStaticResult bmsDailyStaticResult =
                                batteryRunInfoStaticService.staticDailyBattery(batteryPo.getVin(), String.valueOf(batteryPo.getCustomerId()), period);
                        bmsStaticResults.add(bmsDailyStaticResult);
                    } else {
                        //查询车辆一周内的统计数据是否齐全，如果没有，则生成当小时周期内的数据
                        MongoCursor<Document> mongoCursor = mongoDbUtils.getDatabase().getCollection(hourlyStaticCollectionName)
                                .find(and(eq("vin", batteryPo.getVin()), eq("dataDateHour", period)))
                                .iterator();
                        if (mongoCursor != null && mongoCursor.hasNext()) {
                            //该小时周期内数据已经存在。
                            continue;
                        }

                        BmsHourlyStaticResult bmsHourlyStaticResult =
                                batteryRunInfoStaticService.staticHourlyBattery(batteryPo.getVin(), String.valueOf(batteryPo.getCustomerId()), period);
                        bmsStaticResults.add(bmsHourlyStaticResult);
                    }
                }
            } catch (InterruptedException e) {
                log.error("{}", e);
                e.printStackTrace();
            } catch (Exception e) {
                log.error("{}", e);
            } finally {
                lock.unlock();
            }


        }
        return (List<E>) bmsStaticResults;
    }


    /**
     * 返回期限内的的所有天
     *
     * @param offset
     * @return
     */
    public static List<Integer> getPreDates(int offset) {
        Calendar calendar = Calendar.getInstance();
        List<Integer> dates = new ArrayList<>();
        while (offset > 0) {
            calendar.add(Calendar.DATE, -1);
            dates.add(Integer.parseInt(sdf.format(calendar.getTime())));
            offset--;
        }
        return dates;
    }

    public static void main(String[] args) {
        getPreDates(5);
    }

    /**
     * 返回今天之内的所有小时
     *
     * @return
     */
    public static List<Integer> getPreHours() {
        Calendar calendar = Calendar.getInstance();
        List<Integer> dateHours = new ArrayList<>();
        //先往前推一个小时，不计算当前所在的小时
        calendar.add(Calendar.HOUR, -1);
        while (calendar.get(Calendar.DATE) == Calendar.getInstance().get(Calendar.DATE)) {
            dateHours.add(Integer.parseInt(sdf_hour.format(calendar.getTime())));
            calendar.add(Calendar.HOUR, -1);
        }
        return dateHours;
    }

    /**
     * 将数据存入mongodb
     *
     * @param bmsStaticResult
     * @param <T>
     */
    private <T extends BmsStaticResult> void saveBatteryStaticInfoInMongo(T bmsStaticResult) {
        if (bmsStaticResult instanceof BmsDailyStaticResult) {
            Document document = new Document(BeanUtil.beanToMap(bmsStaticResult));
            mongoDbUtils.getDatabase().getCollection(dailyStaticCollectionName).insertOne(document);
        } else if (bmsStaticResult instanceof BmsHourlyStaticResult) {
            Document document = new Document(BeanUtil.beanToMap(bmsStaticResult));
            mongoDbUtils.getDatabase().getCollection(hourlyStaticCollectionName).insertOne(document);
        }

    }

}
